/*
 * This file is part of the BGDB package
 *
 * Copyright (C) 2013 Baligh GUESMI Personnel use.
 *
 * Contact: Baligh GUESMI <gasmiba@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

 #include <tcutil.h>

/** \brief allocation unit size of an extensible string*/
#define TCXSTRUNIT     12

/********************************************//**
 * \brief private function prototypes
 *
 * \param xstr TCXSTR*
 * \param format const char*
 * \param ap va_list
 * \return void
 *
 ***********************************************/
static void tcvxstrprintf(TCXSTR *xstr, const char *format, va_list ap);

/********************************************//**
 * \brief Create an extensible string object.
 *
 * \param void
 * \return TCXSTR*
 *
 ***********************************************/
TCXSTR *tcxstrnew(void){
  TCXSTR *xstr;
  tcmalloc(xstr, sizeof(*xstr));
  tcmalloc(xstr->ptr, TCXSTRUNIT);
  xstr->size = 0;
  xstr->asize = TCXSTRUNIT;
  xstr->ptr[0] = '\0';
  return xstr;
}
/********************************************//**
 * \brief Create an extensible string object from a character string.
 *
 * \param str const char*
 * \return TCXSTR*
 *
 ***********************************************/
TCXSTR *tcxstrnew2(const char *str){
  assert(str);
  TCXSTR *xstr;
  tcmalloc(xstr, sizeof(*xstr));
  int size = strlen(str);
  int asize = tclmax(size + 1, TCXSTRUNIT);
  tcmalloc(xstr->ptr, asize);
  xstr->size = size;
  xstr->asize = asize;
  BG_MEMCPY(xstr->ptr, str, size + 1);
  return xstr;
}
/********************************************//**
 * \brief Create an extensible string object with the initial allocation size.
 *
 * \param asiz int
 * \return TCXSTR*
 *
 ***********************************************/
TCXSTR *tcxstrnew3(int asiz){
  assert(asiz >= 0);
  asiz = tclmax(asiz, TCXSTRUNIT);
  TCXSTR *xstr;
  tcmalloc(xstr, sizeof(*xstr));
  tcmalloc(xstr->ptr, asiz);
  xstr->size = 0;
  xstr->asize = asiz;
  xstr->ptr[0] = '\0';
  return xstr;
}
/********************************************//**
 * \brief Copy an extensible string object.
 *
 * \param xstr const TCXSTR*
 * \return TCXSTR*
 *
 ***********************************************/
TCXSTR *tcxstrdup(const TCXSTR *xstr){
  assert(xstr);
  TCXSTR *nxstr;
  tcmalloc(nxstr, sizeof(*nxstr));
  int asize = tclmax(xstr->size + 1, TCXSTRUNIT);
  tcmalloc(nxstr->ptr, asize);
  nxstr->size = xstr->size;
  nxstr->asize = asize;
  BG_MEMCPY(nxstr->ptr, xstr->ptr, xstr->size + 1);
  return nxstr;
}
/********************************************//**
 * \brief Delete an extensible string object.
 *
 * \param xstr TCXSTR*
 * \return void
 *
 ***********************************************/
void tcxstrdel(TCXSTR *xstr){
  assert(xstr);
  tcfree(xstr->ptr);
  tcfree(xstr);
}
/********************************************//**
 * \brief Concatenate a region to the end of an extensible string object.
 *
 * \param xstr TCXSTR*
 * \param ptr const void*
 * \param size int
 * \return void
 *
 ***********************************************/
void tcxstrcat(TCXSTR *xstr, const void *ptr, int size){
  assert(xstr && ptr && size >= 0);
  int nsize = xstr->size + size + 1;
  if(xstr->asize < nsize){
    while(xstr->asize < nsize){
      xstr->asize *= 2;
      if(xstr->asize < nsize) xstr->asize = nsize;
    }
    tcrealloc(xstr->ptr, xstr->ptr, xstr->asize);
  }
  BG_MEMCPY(xstr->ptr + xstr->size, ptr, size);
  xstr->size += size;
  xstr->ptr[xstr->size] = '\0';
}
/********************************************//**
 * \brief Concatenate a character string to the end of an extensible string object.
 *
 * \param xstr TCXSTR*
 * \param str const char*
 * \return void
 *
 ***********************************************/
void tcxstrcat2(TCXSTR *xstr, const char *str){
  assert(xstr && str);
  int size = strlen(str);
  int nsize = xstr->size + size + 1;
  if(xstr->asize < nsize){
    while(xstr->asize < nsize){
      xstr->asize *= 2;
      if(xstr->asize < nsize) xstr->asize = nsize;
    }
    tcrealloc(xstr->ptr, xstr->ptr, xstr->asize);
  }
  BG_MEMCPY(xstr->ptr + xstr->size, str, size + 1);
  xstr->size += size;
}
/********************************************//**
 * \brief Get the pointer of the region of an extensible string object.
 *
 * \param xstr const TCXSTR*
 * \return const void*
 *
 ***********************************************/
const void *tcxstrptr(const TCXSTR *xstr){
  assert(xstr);
  return xstr->ptr;
}
/********************************************//**
 * \brief Get the size of the region of an extensible string object.
 *
 * \param xstr const TCXSTR*
 * \return int
 *
 ***********************************************/
int tcxstrsize(const TCXSTR *xstr){
  assert(xstr);
  return xstr->size;
}
/********************************************//**
 * \brief Clear an extensible string object.
 *
 * \param xstr TCXSTR*
 * \return void
 *
 ***********************************************/
void tcxstrclear(TCXSTR *xstr){
  assert(xstr);
  xstr->size = 0;
  xstr->ptr[0] = '\0';
}
/********************************************//**
 * \brief Perform formatted output into an extensible string object.
 *
 * \param xstr TCXSTR*
 * \param format const char*
 * \param ...
 * \return void
 *
 ***********************************************/
void tcxstrprintf(TCXSTR *xstr, const char *format, ...){
  assert(xstr && format);
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
}
/********************************************//**
 * \brief Allocate a formatted string on memory.
 *
 * \param format const char*
 * \param ...
 * \return char*
 *
 ***********************************************/
char *tcsprintf(const char *format, ...){
  assert(format);
  TCXSTR *xstr = tcxstrnew();
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
  return tcxstrtomalloc(xstr);
}
/********************************************//**
 * \brief Perform formatted output into an extensible string object.
 *
 * \param xstr TCXSTR*
 * \param format const char*
 * \param ap va_list
 * \return void
 *
 ***********************************************/
static void tcvxstrprintf(TCXSTR *xstr, const char *format, va_list ap){
  assert(xstr && format);
  while(*format != '\0'){
    if(*format == '%'){
      char cbuf[TCNUMBUFSIZ];
      cbuf[0] = '%';
      int cblen = 1;
      int lnum = 0;
      format++;
      while(strchr("0123456789 .+-hlLz", *format) && *format != '\0' &&
            cblen < TCNUMBUFSIZ - 1){
        if(*format == 'l' || *format == 'L') lnum++;
        cbuf[cblen++] = *(format++);
      }
      cbuf[cblen++] = *format;
      cbuf[cblen] = '\0';
      int tlen;
      char *tmp, tbuf[TCNUMBUFSIZ*4];
      switch(*format){
        case 's':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          tcxstrcat2(xstr, tmp);
          break;
        case 'd':
          if(lnum >= 2){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, long long));
          } else if(lnum >= 1){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, long));
          } else {
            tlen = sprintf(tbuf, cbuf, va_arg(ap, int));
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case 'o': case 'u': case 'x': case 'X': case 'c':
          if(lnum >= 2){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned long long));
          } else if(lnum >= 1){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned long));
          } else {
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned int));
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case 'e': case 'E': case 'f': case 'g': case 'G':
          if(lnum >= 1){
            tlen = snprintf(tbuf, sizeof(tbuf), cbuf, va_arg(ap, long double));
          } else {
            tlen = snprintf(tbuf, sizeof(tbuf), cbuf, va_arg(ap, double));
          }
          if(tlen < 0 || tlen > sizeof(tbuf)){
            tbuf[sizeof(tbuf)-1] = '*';
            tlen = sizeof(tbuf);
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case '@':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          while(*tmp){
            switch(*tmp){
              case '&': TCXSTRCAT(xstr, "&amp;", 5); break;
              case '<': TCXSTRCAT(xstr, "&lt;", 4); break;
              case '>': TCXSTRCAT(xstr, "&gt;", 4); break;
              case '"': TCXSTRCAT(xstr, "&quot;", 6); break;
              default:
                if(!((*tmp >= 0 && *tmp <= 0x8) || (*tmp >= 0x0e && *tmp <= 0x1f)))
                  TCXSTRCAT(xstr, tmp, 1);
                break;
            }
            tmp++;
          }
          break;
        case '?':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          while(*tmp){
            unsigned char c = *(unsigned char *)tmp;
            if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
               (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.", c))){
              TCXSTRCAT(xstr, tmp, 1);
            } else {
              tlen = sprintf(tbuf, "%%%02X", c);
              TCXSTRCAT(xstr, tbuf, tlen);
            }
            tmp++;
          }
          break;
        case 'b':
          if(lnum >= 2){
            tlen = tcnumtostrbin(va_arg(ap, unsigned long long), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          } else if(lnum >= 1){
            tlen = tcnumtostrbin(va_arg(ap, unsigned long), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          } else {
            tlen = tcnumtostrbin(va_arg(ap, unsigned int), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case '%':
          TCXSTRCAT(xstr, "%", 1);
          break;
      }
    } else {
      TCXSTRCAT(xstr, format, 1);
    }
    format++;
  }
}

/*************************************************************************************************
 * extensible string (for experts)
 *************************************************************************************************/

/********************************************//**
 * \brief Convert an extensible string object into a usual allocated region.
 *
 * \param xstr TCXSTR*
 * \return void*
 *
 ***********************************************/
void *tcxstrtomalloc(TCXSTR *xstr){
  assert(xstr);
  char *ptr;
  ptr = xstr->ptr;
  tcfree(xstr);
  return ptr;
}
/********************************************//**
 * \brief Create an extensible string object from an allocated region.
 *
 * \param ptr void*
 * \param size int
 * \return TCXSTR*
 *
 ***********************************************/
TCXSTR *tcxstrfrommalloc(void *ptr, int size){
  TCXSTR *xstr;
  tcmalloc(xstr, sizeof(*xstr));
  tcrealloc(xstr->ptr, ptr, size + 1);
  xstr->ptr[size] = '\0';
  xstr->size = size;
  xstr->asize = size;
  return xstr;
}
